﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Mono.Cecil;

using CodeBlock.AMD64;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Mono.Cecil.Cil;

namespace TestApp
{
	class TestApp
	{
		static AssemblyDefinition thisAssembly;
		static ModuleDefinition thisModule;
		static Compiler compiler;

		void Test()
		{
		}

		static void Test(TestApp test)
		{
		} 

		static void Compile(MethodDefinition method)
		{
			var name = method.DeclaringType.FullName + "." + method.Name;

			Console.Write("compiling " + name + "...");
			var assembly = compiler.Compile(method);
			Console.WriteLine("OK");

			Console.Write("saving...");
			File.WriteAllText(name + ".asm", Fasm.Assembly(assembly));
			Console.WriteLine("OK");

			Console.Write("assemblying...");
			Assemble(name + ".asm");
			Console.WriteLine("OK");
		}

		static void SafeCompile(MethodDefinition method)
		{
			try
			{
				Compile(method);
			} catch (Exception error)
			{
				Console.WriteLine("{0}: {1}", error.GetType(), error.Message);
			}
			//try
			//{
			//    Compile(method);
			//} catch (NotImplementedException error)
			//{
			//    Console.WriteLine("Not implemented: {1}", error.GetType(), error.Message);
			//}
		}

		static void TestVector()
		{
			var vectorClass = thisModule.Types.OfType<TypeDefinition>().Single(type => type.FullName == "TestApp.Vector");
			var getSquare = vectorClass.Methods.OfType<MethodDefinition>().Single(method => method.Name == "GetSquare");
			Compile(getSquare);
		}

		static void TestAssembly()
		{
			foreach (TypeDefinition type in thisModule.Types)
				foreach (MethodDefinition method in type.Methods)
					SafeCompile(method);

			Console.ReadKey();
		}

		static void Main(string[] args)
		{
			compiler = new Compiler(new DefaultAssemblyResolver(), (entry) => 
				{
				});
			thisAssembly = AssemblyFactory.GetAssembly("TestApp.exe");
			thisModule = thisAssembly.Modules.OfType<ModuleDefinition>().Single();

			TestAssembly();
			return;

			var thisClass = thisModule.Types.OfType<TypeDefinition>().Single(type => type.FullName == "TestApp.TestApp");
			var fib = thisClass.Methods.OfType<MethodDefinition>().Single(method => method.Name == "Fib");
			Console.Write("compiling...");
			var assembly = compiler.Compile(fib);
			Console.WriteLine("OK");

			Console.Write("saving assembly...");
			using (var temp = new StreamWriter("test.asm"))
			{
				temp.WriteLine("format PE64 GUI DLL");
				temp.WriteLine("entry DllEntryPoint");
				temp.WriteLine(@"include 'export.inc'");
				temp.WriteLine("section '.text' code readable executable");
				temp.WriteLine();

				temp.WriteLine("DllEntryPoint:");
				temp.WriteLine("mov rax,1");
				temp.WriteLine("ret");
				temp.WriteLine();

				temp.WriteLine("Fib:");
				temp.WriteLine("Push RCX");
				temp.WriteLine("Call L__F7B5TestApp_E7B5TestApp_F7B5Fib_F7B5");	//this indicates actual fib code
				temp.WriteLine("ret");
				temp.WriteLine();

				foreach (var instruction in assembly)
					temp.WriteLine(Fasm.Assembly(instruction));

				// temp.WriteLine("ret");

				temp.WriteLine();

				temp.WriteLine("section '.edata' export data readable");
				temp.WriteLine("export 'test.dll',Fib,'Fib'");

				temp.WriteLine("section '.reloc' fixups data discardable");

				temp.Close();
			}
			Console.WriteLine("OK");

			Console.Write("assemblying...");
			Assemble("test.asm");
			Console.WriteLine("OK");

			TestCompiled();
		}

		public delegate ulong FibDelegate(ulong parameter);

		private static void TestCompiled()
		{
			//var ptr = LoadLibrary("test.dll");
			//if (ptr == IntPtr.Zero)
			//    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

			//var fibptr = GetProcAddress(ptr, "Fib");
			//if (fibptr == IntPtr.Zero)
			//    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

			//FibDelegate func = (FibDelegate)Marshal.GetDelegateForFunctionPointer(fibptr, typeof(FibDelegate));

			for (ulong value = 0; value < 60; value++)
			{
				ulong fib = Fib(value);
				ulong cbfib = CodeBlockFib(value);
				Console.WriteLine("{0}: {1} vs {2}", value, fib, cbfib);
			}

			Console.ReadKey();
		}

		static void Assemble(string filename)
		{
			var startInfo = new ProcessStartInfo("fasm.exe", filename);
			startInfo.CreateNoWindow = true;
			startInfo.UseShellExecute = false;
			startInfo.RedirectStandardError = true;

			using (var fasm = Process.Start(startInfo))
			{
				fasm.WaitForExit();
				if (fasm.ExitCode != 0)
					throw new Exception(fasm.StandardError.ReadToEnd());
			}
		}

		public static ulong Fib(ulong n)
		{
			// if (n.CompareTo(0) == 0) return 1;
			if (n == 0) return 1;
			if (n == 1) return 1;
			return Fib(n - 1) + Fib(n - 2);
		}

		[DllImport("test.dll", EntryPoint="Fib", SetLastError=true)]
		static extern ulong CodeBlockFib(ulong n);

		[DllImport("kernel32.dll", SetLastError=true)]
		static extern IntPtr LoadLibrary(string filename);

		[DllImport("kernel32.dll", SetLastError = true)]
		static extern IntPtr GetProcAddress(IntPtr lib, string name);
	}

	public struct Vector
	{
		public int X, Y;
		public int XP
		{
			get { return X; }
			set { X = value; }
		}
		public int YP { get { return Y; } }
		public int GetSquare()
		{
			return XP * XP + YP * YP;
		}
	}
}
